package com.bestvike.portal.service.impl;

import cn.com.higinet.token.lg.LgToken;
import cn.com.higinet.token.lg.LgTokenDriver;
import cn.com.higinet.token.lg.impl.LgTokenDriverTESM3Impl;
import com.bestvike.commons.entity.Route;
import com.bestvike.commons.entity.User;
import com.bestvike.commons.exception.CredentialsException;
import com.bestvike.commons.exception.ServiceException;
import com.bestvike.commons.redis.Cache;
import com.bestvike.commons.utils.DateUtils;
import com.bestvike.commons.utils.EncryptUtils;
import com.bestvike.portal.dao.EssAllotDao;
import com.bestvike.portal.dao.EssPubInfoErrDao;
import com.bestvike.portal.dao.EssSSTokenDao;
import com.bestvike.portal.dao.SysUserDao;
import com.bestvike.portal.data.*;
import com.bestvike.portal.service.AuthorityService;
import com.bestvike.portal.service.BaseService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
public class AuthorityServiceImpl extends BaseService implements AuthorityService {
    @Autowired
    private SysUserDao sysUserDao;
    @Autowired
    private EssAllotDao essAllotDao;
    @Autowired
    private EssSSTokenDao essSSTokenDao;
    @Autowired
    private EssPubInfoErrDao essPubInfoErrDao;

    @Autowired
    @Qualifier("authorityCache")
    private Cache authorityCache;
    @Autowired
    private AuthenticationManager authenticationManager;

    @Value("${app.authority.login.allow-type:any}")
    private String allowType;
    @Value("${server.servlet.session.timeout}")
    private String sessionTimeout;

    @Override
    public SysUser login(String appCode, SysUser sysUser, HttpServletRequest httpServletRequest) throws UnsupportedEncodingException {
        String userName = sysUser.getId();
        if (StringUtils.isEmpty(userName)) {
            throw new CredentialsException("用户名不能为空");
        }
        String loginType = sysUser.getLoginType();
        if (StringUtils.isEmpty(loginType)) {
            loginType = "passtoken";
        }
        if (!allowType.equals("any")) {
            if (!loginType.equals(allowType)) {
                throw new CredentialsException("登录方式异常！");
            }
        }

        String password = sysUser.getPassword();
        String passtoken = sysUser.getPasstoken();
        Example example = new Example(SysUser.class);
        example.createCriteria().andEqualTo("userId", sysUser.getId()).orEqualTo("id", sysUser.getId()).orEqualTo("mobile", sysUser.getId()).orEqualTo("email", sysUser.getId());
        sysUser = sysUserDao.selectOneByExample(example);
        if (sysUser==null) {
            throw new CredentialsException("用户不存在！");
        }
        if (loginType.equals("passtoken") || loginType.equals("both")) {
            // 通过令牌登录
            example = new Example(EssAllot.class);
            example.createCriteria().andEqualTo("userId", sysUser.getId())
                    .andIn("state", Arrays.asList(EssAllot.ESS_ALLOT_STATE_ENABLE, EssAllot.ESS_ALLOT_STATE_LOCKED)); // 已激活/已锁定
            EssAllot essAllot = essAllotDao.selectOneByExample(example);
            if (essAllot == null) {
                throw new CredentialsException("用户暂未配置动态令牌信息");
            }

            String deviceSn = essAllot.getDeviceSn();

            EssSSToken essSSToken = essSSTokenDao.selectByPrimaryKey(deviceSn);
            // 认证设备不存在
            if (essSSToken == null) {
                throw new CredentialsException("03002315", essPubInfoErrDao.selectByPrimaryKey("03002315").getErrInfo());
            }
            // 已锁定
            if (EssSSToken.ESS_TK_LOCKED_CODE.equals(essSSToken.getTkLockFlag())) {
                throw new CredentialsException("03002302", essPubInfoErrDao.selectByPrimaryKey("03002302").getErrInfo());
            }
            // 调用前，查看数据库字段状态 start
            // 已注销
            if (EssSSToken.ESS_TK_CANCELED_CODE.equals(essSSToken.getTkCancelFlag())) {
                throw new CredentialsException("03002278", essPubInfoErrDao.selectByPrimaryKey("03002278").getErrInfo());
            }
            if (!EssSSToken.ESS_TK_ASSIGNED_CODE.equals(essSSToken.getTkAssignedFlag()) || !EssSSToken.ESS_TK_ENABLED_CODE.equals(essSSToken.getTkEnabledFlag())) {
                // 未分配 || 未激活
                throw new CredentialsException("03002273", essPubInfoErrDao.selectByPrimaryKey("03002273").getErrInfo());
            }
            // 已挂失
            if (EssSSToken.ESS_TK_LOSTED_CODE.equals(essSSToken.getTkLostFlag())) {
                throw new CredentialsException("03002381", essPubInfoErrDao.selectByPrimaryKey("03002381").getErrInfo());
            }

            Properties conf = new Properties();
            conf.put("elimit", "10");
            conf.put("stepsize", "60");
            LgTokenDriver driver = new LgTokenDriverTESM3Impl(conf);

            // 数据库字段 end
            TokenBean tb = new TokenBean();
            tb.setPData(essSSToken.getTkPrivateData());
            tb.setSData(essSSToken.getTkSpecialData());

            LgToken token = driver.getToken(deviceSn, essSSToken.getTkPrivateData().getBytes());

            long time = System.currentTimeMillis() / 1000L;

            // 调用验证接口
            int verify = token.verify(time, passtoken, "", null);
            // 重新生成密文串
            token.encPdata(tb.getPData());

            String prv_new = tb.getPDataToString();
            String spe_new = tb.getSDataToString();

            essSSToken.setTkPrivateData(prv_new);
            if (!StringUtils.isEmpty(spe_new)) {
                essSSToken.setTkSpecialData(spe_new);
            }
            String retCode = changeRetCode(verify);
            BigDecimal tkErTotalNum = essSSToken.getTkErTotalNum();
            Timestamp nowTime = new Timestamp(System.currentTimeMillis());
            if (!EssSSToken.ESS_SUCCESS_RESULT_CODE.equals(retCode)) {
                // 连续验证错误
                if (tkErTotalNum != null && tkErTotalNum.compareTo(BigDecimal.ZERO) > 0) {
                    essSSToken.setLastErDate(nowTime);
                    essSSToken.setTkErSingleNum(essSSToken.getTkErSingleNum().add(BigDecimal.ONE));
                    essSSToken.setTkErTotalNum(essSSToken.getTkErTotalNum().add(BigDecimal.ONE));
                    // 连续错误次数大于10
                    if (essSSToken.getTkErTotalNum().compareTo(BigDecimal.TEN) >= 0 || EssSSToken.ESS_LOCKED_RESULT_CODE.equals(retCode)) {
                        // 锁定
                        essSSToken.setTkLockFlag("1");


                        essAllot.setLastModifyDate(DateUtils.getDateDate());
                        essAllot.setLastModifyUser(sysUser.getId() + "_" + sysUser.getName());
                        essAllot.setState(EssAllot.ESS_ALLOT_STATE_LOCKED);


                    }
                    essSSTokenDao.updateByPrimaryKey(essSSToken);
                    essAllotDao.updateByPrimaryKey(essAllot);

                } else { // 首次验证错误
                    essSSToken.setFirstErDate(nowTime);
                    essSSToken.setLastErDate(nowTime);
                    essSSToken.setTkErSingleNum(BigDecimal.ONE);
                    essSSToken.setTkErTotalNum(BigDecimal.ONE);
                    essSSTokenDao.updateByPrimaryKey(essSSToken);
                }
                EssPubInfoErr essPubInfoErr = essPubInfoErrDao.selectByPrimaryKey(retCode);
                throw new CredentialsException(retCode, essPubInfoErr.getErrInfo());
            }
            essSSToken.setFirstErDate(null);
            essSSToken.setLastErDate(null);
            essSSToken.setTkErSingleNum(BigDecimal.ZERO);
            essSSToken.setTkErTotalNum(BigDecimal.ZERO);
            essSSTokenDao.updateByPrimaryKey(essSSToken);

            if (loginType.equals("both")) {
                if (sysUser == null || !new BCryptPasswordEncoder().matches(EncryptUtils.base64Decode(password), sysUser.getPassword())) {
                    throw new CredentialsException("用户名或密码错");
                }
            }
        } else {
            // 通过用户名、密码登录
            if (sysUser == null || !new BCryptPasswordEncoder().matches(EncryptUtils.base64Decode(password), sysUser.getPassword())) {
                throw new CredentialsException("用户名或密码错");
            }
        }

        if (StringUtils.isEmpty(sysUser.getRoles())) {
            throw new CredentialsException("用户无权限");
        }
        if(!sysUser.getStatus().equals("0000")){
            throw new CredentialsException("用户已销户");
        }

        /*SysUser sysUser = sysUserDao.selectByPrimaryKey("1");
        sysUser.setRoles("admin");
        sysUser.setToken("admin");*/

        // 生成spring security oauth2 token
        // SimpleGrantedAuthority authority = new SimpleGrantedAuthority("role_client");

        // 用户路由（根据角色权限合成）
        Map<String, Set<String>> userPermissions = new HashMap<>();
        String[] roles = sysUser.getRoles().split(",");
        // 合并多个角色的路由
        for (String roleId : roles) {
            Map<String, List<String>> permissions = authorityCache.getMap("role_permissions:" + roleId);
            if (permissions != null) {
                permissions.forEach((route, operates) -> {
                    Set<String> routeOperates = userPermissions.get(route);
                    if (routeOperates == null) {
                        if (operates != null) {
                            userPermissions.put(route, new HashSet<>(operates));
                        } else {
                            userPermissions.put(route, null);
                        }
                    } else {
                        if (operates != null) {
                            routeOperates.addAll(operates);
                            userPermissions.put(route, routeOperates);
                        }
                    }
                });
            }
        }

        /*
        {
            "/xxx/xxx1/xxx2": ["create", "modify"],
            "/xxx/xxx1/xxx2": ["create", "remove"]
        }
        =>
        [
          {
            path: "xxx",
            children: [
                {
                    path: "xxx1",
                    children: [
                      {
                        path: "xxx2",
                        operates: ["create", "modify"]
                      },
                      {
                        path: "xxx3",
                        operates: ["create", "remove"]
                      }
                    ]
                }
            ]
          }
        ]
        */

        // 用户路由（前端页面展示菜单用）
        List<Route> routes = authorityCache.get("routes:" + appCode + ":async_routes", List.class);
        List<Route> userRoutes = filterRoutes(routes, userPermissions);

        /*Map<String, List<String>> routes = new HashMap<>();
        // 用户角色权限
        List<String> urls = new ArrayList<>();
        // List<String> urls = new ArrayList<>();
        String[] roles = sysUser.getRoles().split(",");
        for (String roleId : roles) {
            List<String> keys = cache.keys("role_permissions:" + roleId + ":*", String.class);
            if (keys != null && keys.size() > 0) {
                Map<String, List<String>> permissions = new HashMap<>();
                for (String key : keys) {
                    // AUTH-LH:role_permissions:1000:/project/sales:operates
                    String[] arr = key.split(":");
                    String route = arr[arr.length - 2];
                    String type = arr[arr.length - 1];
                    if (type.equals("route")) {
                        if (!routes.containsKey(route)) {
                            routes.put(route, null);
                        }
                        continue;
                    }
                    if (!permissions.containsKey(route)) {
                        List<String> values = new ArrayList<>();
                        values.add(type);
                        permissions.put(route, values);
                    } else {
                        List<String> values = permissions.get(route);
                        values.add(type);
                        permissions.put(route, values);
                    }
                    if (type.equals("operates")) {
                        routes.put(route, new ArrayList<>());
                    } else if (!routes.containsKey(route)) {
                        routes.put(route, null);
                    }
                }
                permissions.forEach((route, values) -> {
                    List<String> configRouteUrls = cache.getList("authority_config:routes:" + route, String.class);
                    if (configRouteUrls != null && configRouteUrls.size() > 0) {
                        urls.addAll(configRouteUrls);
                    }
                    List<String> routeOperates = routes.get(route);
                    values.forEach(value -> {
                        List<String> list = cache.getList("role_permissions:" + roleId + ":" + route + ":" + value, String.class);
                        if (list != null && list.size() > 0) {
                            if (value.equals("operates")) {
                                routeOperates.addAll(list);
                                routes.put(route, routeOperates);
                            } else if (value.equals("urls")) {
                                urls.addAll(list);
                            }
                        }
                    });
                });
            }
        }*/

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sysUser.getId(), password, AuthorityUtils.commaSeparatedStringToAuthorityList(sysUser.getRoles()).stream().collect(Collectors.toSet()));
        Authentication auth = authenticationManager.authenticate(authenticationToken);
        SecurityContext securityContext = SecurityContextHolder.getContext();
        securityContext.setAuthentication(auth);
        HttpSession httpSession = httpServletRequest.getSession(true);
        int timeout = 600;
        if (!StringUtils.isEmpty(sessionTimeout)) {
            if (sessionTimeout.endsWith("s")) {
                timeout = Integer.valueOf(sessionTimeout.substring(0, sessionTimeout.length() - 1));
            } else if (sessionTimeout.endsWith("m")) {
                timeout = 60 * Integer.valueOf(sessionTimeout.substring(0, sessionTimeout.length() - 1));
            } else {
                timeout = Integer.valueOf(sessionTimeout);
            }
        }
        httpSession.setMaxInactiveInterval(timeout);
        httpSession.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
        httpSession.setAttribute("user_id", sysUser.getId());

        sysUser.setToken(httpSession.getId());
        sysUser.setPassword("******");

        User user = new User();
        user.setId(sysUser.getId());
        user.setName(sysUser.getName());
        user.setAvatar(sysUser.getAvatar());
        user.setRoles(sysUser.getRoles());
        user.setCode(sysUser.getUserId());

        authorityCache.set("token_to_user:" + httpSession.getId(), sysUser.getId());
        /// authorityCache.add("user_permissions:" + sysUser.getId(), urls);
        authorityCache.set("user_routes:" + appCode + ":" + sysUser.getId(), userRoutes);
        authorityCache.set("user_details:" + sysUser.getId(), user);

        sysUser.setRoutes(userRoutes);

        return sysUser;
    }

    @Override
    public void logout(HttpServletRequest httpServletRequest) {
        HttpSession httpSession = httpServletRequest.getSession();
        String userId = authorityCache.get("token_to_user:" + httpSession.getId(), String.class);
        authorityCache.delete("token_to_user:" + httpSession.getId());
        if (!StringUtils.isEmpty(userId)) {
            authorityCache.delete("user_permissions:" + userId);
            authorityCache.delete("user_routes:" + userId);
            authorityCache.delete("user_details:" + userId);
        }
    }

    @Override
    public void modifyPassword(SysUser sysUser) throws UnsupportedEncodingException {
        if (StringUtils.isEmpty(sysUser.getPassword())) {
            throw new ServiceException("原密码不能为空");
        }
        if (StringUtils.isEmpty(sysUser.getNewPassword())) {
            throw new ServiceException("新密码不能为空");
        }
        SysUser sysUserDb = sysUserDao.selectByPrimaryKey(sysUser.getId());
        if (sysUserDb == null || !new BCryptPasswordEncoder().matches(EncryptUtils.base64Decode(sysUser.getPassword()), sysUserDb.getPassword())) {
            throw new ServiceException("用户名或密码错");
        }
        sysUser.setPassword(new com.bestvike.commons.crypto.bcrypt.BCryptPasswordEncoder().encode(EncryptUtils.base64Decode(sysUser.getNewPassword())));
        sysUser.setNewPassword(null);
        sysUserDao.updateByPrimaryKeySelective(sysUser);
    }

    @Override
    public SysUser getUserInfo(String appCode, String userId) {
        if (userId != null) {
            SysUser sysUser = sysUserDao.selectByPrimaryKey(userId);
            if (sysUser != null) {
                sysUser.setPassword("******");

                List<Route> routes = authorityCache.get("user_routes:" + appCode + ":" + userId, List.class);
                if (routes != null) {
                    sysUser.setRoutes(routes);
                }

                return sysUser;
            }
        }
        return null;
    }

    @Value("${app.authority.check-route:true}")
    private Boolean checkRoute;
    private List<Route> filterRoutes(List<Route> routes, Map<String, Set<String>> authorityRoutes) {
        List<Route> results = new ArrayList<>();
        for (Route route : routes) {
            Route temp = new Route();
            BeanUtils.copyProperties(route, temp, "children", "authority");
            if (route.getChildren() != null && route.getChildren().size() > 0) {
                temp.setChildren(filterRoutes(route.getChildren(), authorityRoutes));
            }

            if (checkRoute) {
                if (authorityRoutes.containsKey(temp.getId())) {
                    Set<String> operates = authorityRoutes.get(temp.getId());
                    if (operates != null && !operates.isEmpty() && route.getAuthority() != null && route.getAuthority().getOperates() != null) {
                        Route.Meta meta = temp.getMeta();
                        if (meta == null) {
                            meta = new Route.Meta();
                        }
                        List<Route.Operate> authorityOperates = new ArrayList<>();
                        for (Route.Operate operate : route.getAuthority().getOperates()) {
                            if (operates.contains(operate.getName())) {
                                Route.Operate authorityOperate = new Route.Operate();
                                BeanUtils.copyProperties(operate, authorityOperate, "urls");
                                authorityOperates.add(authorityOperate);
                            }
                        }
                        meta.setOperates(authorityOperates);
                        temp.setMeta(meta);
                    }
                    results.add(temp);
                }
            } else {
                Route.Meta meta = temp.getMeta();
                if (meta == null) {
                    meta = new Route.Meta();
                }
                if (route.getAuthority() != null && route.getAuthority().getOperates() != null) {
                    List<Route.Operate> authorityOperates = new ArrayList<>();
                    for (Route.Operate operate : route.getAuthority().getOperates()) {
                        Route.Operate authorityOperate = new Route.Operate();
                        BeanUtils.copyProperties(operate, authorityOperate, "urls");
                        authorityOperates.add(authorityOperate);
                    }
                    meta.setOperates(authorityOperates);
                }
                temp.setMeta(meta);
                results.add(temp);
            }
        }
        return results;
    }

    /**
     * 获取ess错误代码
     *
     * @param ret
     * @return
     */
    private String changeRetCode(int ret) {
        switch (ret) {
            case 3:
                return "03000476";
            case 1:
                return "03000422";
            case 2:
                return "03000472";
            case 11:
                return "03000494";
            case 12:
                return "03000485";
            case 13:
                return "03000426";
            case 14:
                return "03000474";
            case 20:
                return "03000473";
            case 21:
                return "03000402";
            case 10:
                return "03000010";
            case 51:
                return "03000485";
            case 52:
                return "03000485";
        }
        return "0" + String.valueOf(ret + 3000000);
    }
}
